/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package fr.inria.openmath.omapi.implementation;

/**
 *
 * @author Mr.Sam
 */
import java.io.*;
import java.lang.*;
import java.math.BigInteger;
import fr.inria.openmath.omapi.*;

public class XMLParser extends ParserAdapter implements Parser, Locator {

    boolean initDone = false;

    ParserHandler parserHandler;

    ParserErrorHandler parserErrorHandler;

    UTF7Codec utf7;

    // the skipObject is called in a non blocking way thus must store parsing state
    boolean skipInTag;

    char skipTag[] = null;

    int skipPos;

    InputStream skipSource;

    // input buffer for token aggregation
    final static int BUFFER_MAX_RESTART_CAPACITY = 2 ^ 12;

    final static int BUFFER_START_CAPACITY = 2 ^ 10;

    final static int BUFFER_GROW_MULT = 1;

    final static int BUFFER_GROW_MAX_INCREMENT = 2 ^ 16;

    char[] buffer;

    int bufferCapacity;

    int bufferLen;

    // int parsing
    int intSign;

    // for locator
    int line;

    int column;

    // optimized-one-char-look-ahead for input stream
    final static int HEAD_EMPTY = 0;

    final static int HEAD_READY = 1;

    final static int HEAD_UNGET = 2;

    char sourceHead;

    int sourceHeadState;

    // BASE64 codec
    //    Base64Encoder b64Enc=new Base64Encoder();
    Base64Decoder b64Dec = new Base64Decoder();

    public XMLParser (ParserHandler parserHandler,
            ParserErrorHandler parserErrorHandler) {
        this.parserHandler = parserHandler;
        this.parserErrorHandler = parserErrorHandler;
        this.utf7 = new UTF7Codec();
    }

    public XMLParser (ParserHandler parserHandler) {
        this(parserHandler, new ParserErrorAdapter());
    }

    public XMLParser () {
        this(new ParserAdapter(), new ParserErrorAdapter());
    }


    private char lookChar(InputStream source) throws IOException {
        int i;

        if (sourceHeadState == HEAD_EMPTY) {
            if ((i = source.read()) < 0) {
                throw new IOException("EOF");
            }
            sourceHead = (char) i;
            if (sourceHead == '\n') {
                line++;
                column = 0;
            } else {
                column++;
            }
            sourceHeadState = HEAD_READY;
        } else if (sourceHeadState == HEAD_UNGET) {
            sourceHeadState = HEAD_READY;
        }
        return sourceHead;
    }

    private char getChar(InputStream source) throws IOException {
        int i;
        char c;

        if (sourceHeadState == HEAD_EMPTY) {
            if ((i = source.read()) < 0) {
                throw new IOException("EOF");
            }
            c = (char) i;
            if (c == '\n') {
                line++;
                column = 0;
            } else {
                column++;
            }
            return c;
        } else if (sourceHeadState == HEAD_READY) {
            sourceHeadState = HEAD_EMPTY;
            return sourceHead;
        } else {                       // if (sourceHeadState==HEAD_UNGET){
            sourceHeadState = HEAD_EMPTY;
            return sourceHead;
        }
    }

    private void ungetChar(char c) throws IOException {
        if (sourceHeadState == HEAD_EMPTY) {
            sourceHead = c;
            sourceHeadState = HEAD_UNGET;
        } else {
            throw new IOException("can't unget twice...");
        }
    }

    private void resetBuffer() {
        bufferLen = 0;
        if (bufferCapacity > BUFFER_MAX_RESTART_CAPACITY) {
            buffer = new char[BUFFER_MAX_RESTART_CAPACITY];
            bufferCapacity = BUFFER_MAX_RESTART_CAPACITY;
        }
    }

    private void resizeBuffer() {
        char[] newBuffer;
        if (bufferCapacity != 0) {
            bufferCapacity += Math.min(BUFFER_GROW_MAX_INCREMENT,
                    bufferCapacity * BUFFER_GROW_MULT);
            newBuffer = new char[bufferCapacity];
            System.arraycopy(buffer, 0, newBuffer, 0, bufferLen);
            buffer = newBuffer;
        } else {
            bufferCapacity = BUFFER_START_CAPACITY;
            buffer = new char[bufferCapacity];
        }
    }

    private String getBuffer() {
        String s;
        s = new String(buffer, 0, bufferLen);
        resetBuffer();
        return s;
    }

    private void appendBuffer(char c) {
        if (bufferLen == bufferCapacity) {
            resizeBuffer();
        }
        buffer[bufferLen++] = c;
    }

    private boolean isTag(char c) {
        return (c >= 'A' && c <= 'Z') || (c == '/') || (c == '!') || (c == '-')
                || (c == '?');
    }

    private boolean isDigit(char c) {
        return (c >= '0') && (c <= '9');
    }

    private boolean isHexDigit(char c) {
        return (c >= '0') && (c <= '9') || (c >= 'A' && c <= 'F')
                || (c >= 'a' && c <= 'f');
    }

    private final static boolean isSeparator(char c) {
        return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r');
    }

    private void parseSeparator(InputStream source)
            throws OMException, IOException {
        char c;

        while (true) {                 // skip all sperators and update locator
            c = getChar(source);
            if ((c == ' ') || (c == '\r') || (c == '\t')) {} else if
                    ((c == '\n')) {
                line++;
            } else {
                ungetChar(c);
                break;
            }
        }
    }

    private void throwOME(String message, Exception e) throws OMException {
        OMException ome =
                new OMException(
                message + " (at line:" + line + " col:" + column + ")", e);
        if (parserErrorHandler != null) {
            parserErrorHandler.fatalError(ome);
        } else {
            throw ome;
        }
    }

    private void throwOME(String message) throws OMException {
        throwOME(message, (Exception) null);
    }


    private void parseEqual(InputStream source) throws OMException, IOException
            {
        char c;

        parseSeparator(source);
        c = getChar(source);
        if (c != '=') {
            ungetChar(c);
            throwOME("can't find '='.");
        }
        parseSeparator(source);
    }

    private void parseQuote(InputStream source) throws OMException, IOException
            {
        char c;

        c = getChar(source);
        if (c != '"' && c != '\'') {
            ungetChar(c);
            throwOME("can't find \" or '.");
        }
    }

    private void parseClosing(InputStream source)
            throws OMException, IOException {
        char c;

        parseSeparator(source);
        if ((c = getChar(source)) == '>') {
            return;
        } else if (c == '/') {
            if ((c = getChar(source)) == '>') {
                return;
            } else {
                ungetChar(c);          // lost / but should not matter
                throwOME("missing '>' in current tag.");
            }
        } else {
            ungetChar(c);
            throwOME("missing '>' in current tag.");
        }
    }

    private void parseKnownString(InputStream source, String s)
            throws OMException, IOException {
        int i, l;
        char c;

        l = s.length();
        for (i = 0; i < l; i++) {
            if ((c = getChar(source)) != s.charAt(i)) {
                ungetChar(c);
                throwOME("can't find string " + s + " in current tag.");
            }
        }
    }

    private String parseQuotedString(InputStream source)
            throws OMException, IOException {
        char c;

        if (((c = getChar(source)) != '"') && (c != '\'')) {
            ungetChar(c);
            throwOME("can't find opening \" or '.");
        }

        /* while we don't see a " */
        while (((c = getChar(source)) != '"') && (c != '\'')) {
            appendBuffer(c);
        }
        return getBuffer();
    }

    private String parseToken(InputStream source)
            throws OMException, IOException {
        char c;

        while (!(isSeparator(c = getChar(source)) || (c == '"')
                || (c == '\''))) {
            appendBuffer(c);
        }
        ungetChar(c);
        return getBuffer();
    }


    private void parseEndOMI(InputStream source)
            throws OMException, IOException {
        parseSeparator(source);
        parseKnownString(source, "</OMI>");
    }

    private void parseIntBase10(InputStream source)
            throws OMException, IOException {
        String digits;
        int len, cs, iv = 0;
        boolean is32;
        char c;

        while (true) {
            c = getChar(source);
            if (isDigit(c)) {
                appendBuffer(c);
            } else if (c == '<') {
                ungetChar(c);
                break;
            } else if (!isSeparator(c)) {
                ungetChar(c);
                throwOME("not a valid int token. (found non decimal '" + c +
                        "' digit)");
                break;
            }
        }

        parseEndOMI(source);
        digits = getBuffer();
        len = digits.length();
        // Does that decimal int fit in 32 bits?
        // 32 bits ints converted in decimal are in the range
        // [ -2147483648 .. 2147483647 ] 10 digits at most
        if (len < 10) {
            is32 = true;
            if (intSign >= 0) {
                iv = Integer.parseInt(digits);
            } else {
                iv = -Integer.parseInt(digits);
            }
        } else if (len > 10) {
            is32 = false;
        } else {
            if (intSign >= 0) {
                if (is32 = (digits.compareTo("2147483648") < 0)) {
                    iv = Integer.parseInt(digits);
                }
            } else {
                cs = digits.compareTo("2147483648");
                if (cs < 0) {
                    is32 = true;
                    iv = -Integer.parseInt(digits);
                } else if (cs == 0) {/* special case for MIN_INT */
                    is32 = true;
                    iv = Integer.MIN_VALUE;
                } else {
                    is32 = false;
                }
            }
        }

        if (is32) {
            parserHandler.readInteger(iv);
        } else {
            if (intSign >= 0) {
                parserHandler.readInteger(new BigInteger(digits));
            } else {
                parserHandler.readInteger((new BigInteger(digits)).negate());
            }
        }
    }

    private void parseIntBase16(InputStream source)
            throws OMException, IOException {
        String digits;
        int vi;
        char c;

        while (true) {
            c = getChar(source);
            if (isHexDigit(c)) {
                appendBuffer(c);
            } else if (c == '<') {
                ungetChar(c);
                break;
            } else if (!isSeparator(c)) {
                ungetChar(c);
                throwOME("not a valid int token. (found non hexadecimal '" + c
                        + "' digit)");
            }
        }

        parseEndOMI(source);
        digits = getBuffer();
        // assume an hexa is always an int 32 but put a fallback for bigintegers just in case :)
        try {
            vi = Integer.parseInt(digits, 16);
            if (intSign >= 0) {
                parserHandler.readInteger(vi);
            } else {
                parserHandler.readInteger(-vi);
            }
        } catch (NumberFormatException e) {
            // this wasn't a native 32 bit int
            if (intSign >= 0) {
                parserHandler.readInteger(new BigInteger(digits, 16));
            } else {
                parserHandler.readInteger(
                        (new BigInteger(digits, 16)).negate());
            }
        }
    }

    private void parseInt(InputStream source) throws OMException, IOException {
        char c;

        parseSeparator(source);
        c = getChar(source);
        if (c == '-') {
            intSign = -1;
            parseSeparator(source);
            c = getChar(source);
        } else {
            intSign = 1;
        }

        if (c == 'x') {
            parseIntBase16(source);
            return;
        } else if (isDigit(c)) {
            ungetChar(c);
            parseIntBase10(source);
            return;
        } else {
            ungetChar(c);
            throwOME("not a valid int token. (found '" + c + "' digit)");
        }
    }

    private void parseSymbol(InputStream source)
            throws OMException, IOException {
        String name, cd;
        char c;

        parseSeparator(source);
        c = getChar(source);
        switch (c) {
            case 'n': 
                parseKnownString(source, "ame");
                parseEqual(source);
                name = parseQuotedString(source);
                parseSeparator(source);
                parseKnownString(source, "cd");
                parseEqual(source);
                cd = parseQuotedString(source);
                parseClosing(source);
                parserHandler.readSymbol(new Symbol(cd, name));
                break;
            case 'c': 
                parseKnownString(source, "d");
                parseEqual(source);
                cd = parseQuotedString(source);
                parseSeparator(source);
                parseKnownString(source, "name");
                parseEqual(source);
                name = parseQuotedString(source);
                parseClosing(source);
                parserHandler.readSymbol(new Symbol(cd, name));
                break;
            default: 
                ungetChar(c);
                throwOME("find unexpected content in this symbol element.");
        }
    }

    private void parseString(InputStream source)
            throws OMException, IOException {
        char c;

        while ((c = getChar(source)) != '<') {
            appendBuffer(c);
        }
        parseKnownString(source, "/OMSTR>");
        parserHandler.readString(utf7.decodeString(getBuffer()));
    }

    private void parseFloat64Dec(InputStream source)
            throws OMException, IOException {
        Double d;
        String enc;

        enc = parseToken(source);
        parseSeparator(source);
        parseQuote(source);
        parseClosing(source);
        try {
            d = Double.valueOf(enc);
            parserHandler.readFloat(d.doubleValue());
        } catch (NumberFormatException e) {
            if (enc.equalsIgnoreCase("Infinity")) {
                parserHandler.readFloat(Double.POSITIVE_INFINITY);
            } else if (enc.equalsIgnoreCase("-Infinity")) {
                parserHandler.readFloat(Double.NEGATIVE_INFINITY);
            } else if (enc.equalsIgnoreCase("NaN")) {
                parserHandler.readFloat(Double.NaN);
            } else {
                throwOME("not a valid float token.", e);
            }
        }
    }


    private long parseLongHexInvertEndianess(String s) throws OMException {
        long result = 0;
        int i = 0, sl, MSNib, LSNib;

        if (s == null) {
            throwOME("internal error? (null pointer).");
        }
        sl = s.length();
        if (sl != 16) {
            throwOME
                    (
                    "error in base 16 encoding, a word must be 16 digits long ("
                    + s + ").");
        }

        for (i = 7; i >= 0; i--) {
            MSNib = Character.digit(s.charAt(i * 2), 16);
            LSNib = Character.digit(s.charAt(i * 2 + 1), 16);
            if (LSNib < 0) {
                throw new NumberFormatException(
                        "error in base 16 encoding, wrong digit '" + s.charAt(
                        i * 2 + 1) + "'. (" + s + ")");
            }
            if (MSNib < 0) {
                throw new NumberFormatException(
                        "error in base 16 encoding, wrong digit '" + s.charAt(
                        i * 2) + "'. (" + s + ")");
            }
            result = (result << 8) | (MSNib << 4) | LSNib;
        }
        return result;
    }


    private void parseFloat64Hex(InputStream source)
            throws OMException, IOException {
        double d;
        String enc;
        long bits;

        enc = parseToken(source);
        parseSeparator(source);
        parseQuote(source);
        parseClosing(source);
        bits = parseLongHexInvertEndianess(enc);
        d = java.lang.Double.longBitsToDouble(bits);
        parserHandler.readFloat(d);
    }

    private void parseFloat64(InputStream source)
            throws OMException, IOException {
        char c;

        parseSeparator(source);
        c = getChar(source);
        switch (c) {
            case 'h': 
                parseKnownString(source, "ex");
                parseEqual(source);
                parseQuote(source);
                parseSeparator(source);
                parseFloat64Hex(source);
                break;
            case 'd': 
                parseKnownString(source, "ec");
                parseEqual(source);
                parseQuote(source);
                parseSeparator(source);
                parseFloat64Dec(source);
                break;
            default: 
                ungetChar(c);
                throwOME("unknown floating point encoding.");
        }
    }

    private void parseVariable(InputStream source)
            throws OMException, IOException {
        String name;

        parseSeparator(source);
        parseKnownString(source, "name");
        parseEqual(source);
        name = parseQuotedString(source);
        parserHandler.readVariable(name);
        parseClosing(source);
    }

    private void parseByteArray(InputStream source)
            throws OMException, IOException {
        char c;
        String dec = null;

        while ((c = getChar(source)) != '<') {
            appendBuffer(c);           // OPTIM decode on the fly
        }
        parseKnownString(source, "/OMB>");
        try {
            dec = b64Dec.processString(getBuffer());
        } catch (Base64FormatException e) {
            throwOME("error in base 64 encoding.", e);
        }
        parserHandler.readByteArray(dec.getBytes());
    }

    private void parseComment(InputStream source)
            throws OMException, IOException {
        int inDash = 0;
        char c;

        while (true) {
            c = getChar(source);
            if (c == '-') {
                inDash++;
            } else if ((c == '>') && (inDash >= 2)) {
                break;
            } else {
                inDash = 0;
            }
            appendBuffer(c);
        }
        bufferLen -= 2;
        parserHandler.readComment(getBuffer());
    }

    private void parsePInstruction(InputStream source)
            throws OMException, IOException {
        boolean inMark = false;
        char c;

        while (true) {
            c = getChar(source);
            if (c == '?') {
                inMark = true;
            } else if ((c == '>') && (inMark)) {
                break;
            } else {
                inMark = false;
            }
            appendBuffer(c);
        }
        bufferLen--;
        parserHandler.readPInstruction(getBuffer());
    }

    private void parseElementAux(InputStream source)
            throws OMException, IOException {
        char c, tag[];
        int l, i;

        parseSeparator(source);
        c = getChar(source);
        if (c != '<') {
            ungetChar(c);
            throwOME("can't find a '<'.");
        }

        // collect tag characters
        while (isTag(c = getChar(source))) {
            appendBuffer(c);
        }
        ungetChar(c);
        l = bufferLen;
        tag = buffer;
        bufferLen = 0;
        if (tag[0] == 'O') {
            if (tag[1] == 'M') {
                switch (tag[2]) {
                    case 'A': 
                        if (l == 3) {
                            // OMA 
                            parseClosing(source);
                            parserHandler.startApplication();
                            return;
                        } else if ((l == 6) && tag[3] == 'T' && tag[4] == 'T'
                                && tag[5] == 'R') {
                            // OMATTR 
                            parseClosing(source);
                            parserHandler.startAttribution();
                            return;
                        } else if ((l == 5) && tag[3] == 'T' && tag[4] == 'P')
                                {
                            // OMATP 
                            parseClosing(source);
                            parserHandler.startAttributePairs();
                            return;
                        }
                        break;
                    case 'S': 
                        if (l == 3) {
                            // OMS 
                            parseSymbol(source);
                            return;
                        } else if ((l == 5) && tag[3] == 'T' && tag[4] == 'R')
                                {
                            // OMSTR 
                            parseClosing(source);
                            parseString(source);
                            return;
                        }
                        break;
                    case 'V': 
                        if (l == 3) {
                            // OMV 
                            parseVariable(source);
                            return;
                        }
                        break;
                    case 'I': 
                        if (l == 3) {
                            // OMI
                            parseClosing(source);
                            parseInt(source);
                            return;
                        }
                        break;
                    case 'F': 
                        if (l == 3) {
                            // OMF 
                            parseFloat64(source);
                            return;
                        }
                        break;
                    case 'B': 
                        if (l == 3) {
                            // OMB 
                            parseClosing(source);
                            parseByteArray(source);
                            return;
                        } else if ((l == 6) && tag[3] == 'V' && tag[4] == 'A'
                                && tag[5] == 'R') {
                            // OMBVAR
                            parseClosing(source);
                            parserHandler.startBVars();
                            return;
                        } else if ((l == 6) && tag[3] == 'I' && tag[4] == 'N'
                                && tag[5] == 'D') {
                            // OMBIND
                            parseClosing(source);
                            parserHandler.startBind();
                            return;
                        }
                        break;
                    case 'E': 
                        if (l == 3) {
                            // OME 
                            parseClosing(source);
                            parserHandler.startError();
                            return;
                        }
                        break;
                    case 'O': 
                        if ((l == 5) && tag[3] == 'B' && tag[4] == 'J') {
                            // OMOBJ
                            parseClosing(source);
                            skipTag = null;
                            parserHandler.startObject();
                            return;
                        }
                        break;
                }
            }
        } else if (tag[0] == '/') {
            // a closing tag 
            if ((tag[1] == 'O') && (tag[2] == 'M')) {
                switch (tag[3]) {
                    case 'A': 
                        if (l == 4) {
                            // /OMA 
                            parseClosing(source);
                            parserHandler.endApplication();
                            return;
                        } else if ((l == 7) && tag[4] == 'T' && tag[5] == 'T'
                                && tag[6] == 'R') {
                            // /OMATTR 
                            parseClosing(source);
                            parserHandler.endAttribution();
                            return;
                        } else if ((l == 6) && tag[4] == 'T' && tag[5] == 'P')
                                {
                            // /OMATP
                            parseClosing(source);
                            parserHandler.endAttributePairs();
                            return;
                        }
                        break;
                    case 'B': 
                        if (l == 7 && tag[4] == 'V' && tag[5] == 'A'
                                && tag[6] == 'R') {
                            // /OMBVAR 
                            parseClosing(source);
                            parserHandler.endBVars();
                            return;
                        } else if (l == 7 && tag[4] == 'I' && tag[5] == 'N'
                                && tag[6] == 'D') {
                            // /OMBIND
                            parseClosing(source);
                            parserHandler.endBind();
                            return;
                        }
                        break;
                    case 'E': 
                        // /OME 
                        if (l == 4) {
                            parseClosing(source);
                            parserHandler.endError();
                            return;
                        }
                        break;
                    case 'O': 
                        if ((l == 6) && tag[4] == 'B' && tag[5] == 'J') {
                            // /OMOBJ 
                            parseClosing(source);
                            parserHandler.endObject();
                            throw new OMException("end of object",
                                    OMException.OME_END_OBJECT);
                        }
                        break;
                }
            }
        } else if ((l == 3) && tag[0] == '!' && tag[1] == '-' && tag[2] == '-')
                {
            // <!-- comment -->
            parseComment(source);
            return;
        } else if (tag[0] == '?') {
            // <? processing instruction ?>
            // fake rereading the PI head without the leading '?'
            for (i = 0; i < l; i++) {
                buffer[i] = buffer[i + 1];
            }
            bufferLen = l - 1;
            parsePInstruction(source);
            return;
        }
        // nothing matches
        throwOME("unknown tag '" + new String(tag, 0, l) + "'.");
    }


    /**
     * @see fr.inria.openmath.omapi.Parser#initParse
     */
    public void initParse(InputStream source) throws OMException {
        initDone = true;
        line = 0;
        column = 0;
        skipTag = null;
        parserHandler.setLocator(this);
        parserHandler.startParse();
    }

    private void checkInit() {
        if (!initDone) {
            throwOME("initParse() has not been called!");
        }
    }

    /**
     * @see fr.inria.openmath.omapi.Parser#parseElement
     */
    public void parseElement(InputStream source) throws OMException {
        checkInit();
        try {
            parseElementAux(source);
        } catch (IOException e) {
            parserHandler.endParse();
        } catch (OMException e) {
            if (e.getErrorCode() != OMException.OME_END_OBJECT)
                throw e;
        }
    }

    /**
     * @see fr.inria.openmath.omapi.Parser#parseObject
     */
    public void parseObject(InputStream source) throws OMException {
        checkInit();
        try {
            while (true) {
                parseElementAux(source);
            }
        } catch (IOException e) {
            parserHandler.endParse();
        } catch (OMException e) {
            if (e.getErrorCode() != OMException.OME_END_OBJECT)
                throw e;
        }
    }

    /**
     * @see fr.inria.openmath.omapi.Parser#parse
     */
    public void parse(InputStream source) throws OMException {
        checkInit();
        try {
            while (true) {
                try {
                    while (true) {
                        parseElementAux(source);
                    }
                } catch (OMException e) {
                    if (e.getErrorCode() != OMException.OME_END_OBJECT)
                        throw e;
                }
            }
        } catch (IOException e) {
            parserHandler.endParse();
        }
    }


    /**
     * Try to skip current object.
     *
     * <p>Skip current object without parsing handler callbacks.
     * This may be a good way to throw away the end of an erroneous
     * object to restart a parse with next one on stream.
     * This is a non blocking call. You may have to call this function
     * several times if object is not available on input stream.
     * @return true if the end of current object has been reached.
     * </p>
     */
    public boolean skipObject(InputStream source) throws OMException {
        char c;
        boolean endSkip = false;

        // This function searches for the </OMOBJ> tag
        // and skips everything on the input stream before
        // such a tag.


        checkInit();
        if (skipTag == null) {
            skipTag = new char[6];
            skipInTag = false;
            skipPos = 0;
        }
        // not reentrant on different input source
        // warn the user if he is mangling two inputs
        if (skipSource != null && source != skipSource) {
            throwOME
                    (
                    "skiping on two different sources with the same parser object!"
                    );
            return false;
        }
        try {
            while (source.available() > 0) {
                c = getChar(source);
                if (skipInTag) {
                    // we are in a tag context
                    // collect tag characters
                    if (c == '>') {
                        // was this tag a /OMOBJ ?
                        if ((skipPos == 6) && (skipTag[0] == '/')
                                && (skipTag[1] == 'O') && (skipTag[2] == 'M')
                                && (skipTag[3] == 'O') && (skipTag[4] == 'B')
                                && (skipTag[5] == 'J'))
                            endSkip = true;
                        skipInTag = false;
                        if (endSkip) {
                            skipSource = null;
                            // this was the end of an object!
                            return true;
                        }
                    } else if (c == '<') {
                        // this wasn't a real tag (comment or garbaged tag)
                        // restart a new tag
                        skipPos = 0;
                    } else if (skipPos >= 6) {
                        // this wasn't a OMOBJ tag (comment or garbaged tag)
                        // jump out of tag context
                        skipInTag = false;
                    } else {
                        // add this new tag character
                        skipTag[skipPos] = c;
                        skipPos++;
                    }
                } else if (c == '<') {
                    // start a new tag
                    skipInTag = true;
                    skipPos = 0;
                } else {
                    // a non tag character
                    getChar(source);
                }
            }
        } catch (IOException e) {
            parserHandler.endParse();
        }
        // end of available input
        // still no </OMOBJ> detected
        return false;
    }



    /**
     * @see fr.inria.openmath.omapi.Locator#getLineNumber
     */
    public int getLineNumber() {
        return line;
    }

    /**
     * @see fr.inria.openmath.omapi.Locator#getColumnNumber
     */
    public int getColumnNumber() {
        return column;
    }

    /**
     * @see fr.inria.openmath.omapi.Parser#setParserHandler
     */
    public void setParserHandler(ParserHandler handler) {
        this.parserHandler = handler;
    }

    /**
     * @see fr.inria.openmath.omapi.Parser#getParserHandler
     */
    public ParserHandler getParserHandler() {
        return this.parserHandler;
    }

    /**
     * @see fr.inria.openmath.omapi.Parser#setParserErrorHandler
     */
    public void setParserErrorHandler(ParserErrorHandler handler) {
        this.parserErrorHandler = handler;
    }

    /**
     * @see fr.inria.openmath.omapi.Parser#getParserErrorHandler
     */
    public ParserErrorHandler getParserErrorHandler() {
        return this.parserErrorHandler;
    }


}
